/*
数据库操作
数据库分2个表，一个BookInfo存放书籍信息：
    不论是否加入书架，只要打开某本书，就会建立BookInfo信息。其中存在show字段，表示是否显示在书架上。
BookContent表用来存放章节：
    不论是否加入书架，只要打开某本书，就会建立章节信息。
 */
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';

final String sql_book_content = 'book_content';
final String sql_id = 'id';
final String sql_bookid = 'bookid';
final String sql_chaptername = 'chaptername';
final String sql_content = 'content';
final String sql_nextid = 'nextid';
final String sql_link = 'link';
final String sql_show = 'show';
final String sql_keyword = 'keyword';
final String sql_index = 'chapterindex';

final String sql_book_info = 'book_info';
final String sql_showname = 'showname';
final String sql_source = 'source';
final String sql_cataloglink = 'cataloglink';
final String sql_imagelink = 'imagelink';
final String sql_intro = 'intro';
final String sql_author = 'author';
final String sql_lastread = 'lastread';
final String sql_lastContentId = 'lastcontentid';
final String sql_lastUrl = "lasturl";

class BookInfo {
  int id; //ID
  String showname = ''; //显示名称
  String source = ''; //来源
  String cataloglink = ''; //目录地址
  String imagelink = ''; //封面图地址
  String intro = ''; //简介
  String author = ''; //作者
  String lastread = ''; //最后阅读时间
  String lastUrl = ''; //最后阅读URL
  int keyword; //最后阅读关键字
  bool show; //是否加入书架
  int lastcontentid; //数据库最新一章ID，判断是否有新

  Map<String, dynamic> toMap() {
    var map = <String, dynamic>{
      sql_showname: showname,
      sql_source: source,
      sql_cataloglink: cataloglink,
      sql_imagelink: imagelink,
      sql_intro: intro,
      sql_author: author,
      sql_lastread: lastread,
      sql_lastUrl: lastUrl,
      sql_keyword: keyword,
      sql_show: show ? 1 : 0,
      sql_lastContentId: lastcontentid
    };
    if (id != null) {
      map[sql_id] = id;
    }
    return map;
  }

  BookInfo();

  BookInfo.fromMap(Map<String, dynamic> map) {
    id = map[sql_id];
    showname = map[sql_showname];
    source = map[sql_source];
    cataloglink = map[sql_cataloglink];
    imagelink = map[sql_imagelink];
    intro = map[sql_intro];
    author = map[sql_author];
    lastread = map[sql_lastread];
    lastUrl = map[sql_lastUrl];
    keyword = map[sql_keyword];
    show = map[sql_show] == 1;
    lastcontentid = map[sql_lastContentId];
  }
}

class BookContent {
  int id; //ID
  int bookid; //所属书籍ID
  String chaptername; //章节名称
  String content; //章节内容
  int nextid; //下章ID（已弃用）
  String link; //章节地址
  String source; //来源
  int chapterindex; //章节序号

  Map<String, dynamic> toMap() {
    var map = <String, dynamic>{
      sql_bookid: bookid,
      sql_chaptername: chaptername,
      sql_content: content,
      sql_nextid: nextid,
      sql_link: link,
      sql_source: source,
      sql_index: chapterindex
    };
    if (id != null) {
      map[sql_id] = id;
    }
    return map;
  }

  BookContent();

  BookContent.fromMap(Map<String, dynamic> map) {
    id = map[sql_id];
    bookid = map[sql_bookid];
    chaptername = map[sql_chaptername];
    content = map[sql_content];
    nextid = map[sql_nextid];
    link = map[sql_link];
    source = map[sql_source];
    chapterindex = map[sql_index];
  }
}

class BookInfoProvider {
  Future open() async {
    var databasesPath = await getDatabasesPath();
    String path = join(databasesPath, "read.db");
    Database db = await openDatabase(path, version: 1,
        onCreate: (Database db, int version) async {
      await db.execute('''
	 CREATE TABLE $sql_book_info (
	 $sql_id INTEGER NOT NULL primary key AUTOINCREMENT,
	 $sql_showname TEXT NOT NULL,
	 $sql_source TEXT NOT NULL,
	 $sql_cataloglink TEXT NOT NULL,
	 $sql_imagelink TEXT,
	 $sql_intro TEXT,
	 $sql_author TEXT,
	 $sql_lastread TEXT,
	 $sql_lastUrl TEXT,
	 $sql_keyword INTEGER,
	 $sql_link TEXT,
	 $sql_show INTEGER NOT NULL DEFAULT 0,
	 $sql_lastContentId INTEGER NOT NULL DEFAULT -1
);
      ''');
      await db.execute('''
CREATE TABLE $sql_book_content (
	 $sql_id INTEGER NOT NULL primary key AUTOINCREMENT,
	 $sql_bookid INTEGER,
	 $sql_chaptername TEXT,
	 $sql_content TEXT,
	 $sql_nextid INTEGER,
	 $sql_link TEXT,
	 $sql_source TEXT,
	 	 $sql_index INTEGER NOT NULL DEFAULT 0

	 );
      ''');
    });
    return db;
  }

  Future<BookInfo> insert(BookInfo info) async {
    Database db = await open();

    info.id = await db.insert(sql_book_info, info.toMap());
    //db.close();

    return info;
  }

  Future<List<BookInfo>> getBookShelf() async {
    Database db = await open();
    List<Map> maps = await db.query(sql_book_info,
        columns: [
          sql_id,
          sql_showname,
          sql_source,
          sql_cataloglink,
          sql_imagelink,
          sql_intro,
          sql_author,
          sql_lastread,
          sql_lastUrl,
          sql_keyword,
          sql_show,
          sql_lastContentId,
        ],
        where: "$sql_show = ?",
        whereArgs: [1],
        orderBy: "$sql_lastread");
    List<BookInfo> books = [];

    for (Map<String, dynamic> map in maps) {
      books.add(BookInfo.fromMap(map));
    }
    return books;
  }

  Future<BookInfo> getBookInfoWithLink(String link) async {
    Database db = await open();

    List<Map> maps = await db.query(sql_book_info,
        columns: [
          sql_id,
          sql_showname,
          sql_source,
          sql_cataloglink,
          sql_imagelink,
          sql_intro,
          sql_author,
          sql_lastread,
          sql_lastUrl,
          sql_keyword,
          sql_show,
          sql_lastContentId,
        ],
        where: "$sql_cataloglink = ?",
        whereArgs: [link]);
    //db.close();

    if (maps.length > 0) {
      return BookInfo.fromMap(maps.first);
    }
    return null;
  }

  Future<int> delete(String link) async {
    Database db = await open();
    int res = await db
        .delete(sql_book_info, where: "$sql_cataloglink=?", whereArgs: [link]);
    //db.close();

    return res;
  }

  Future<int> update(BookInfo info) async {
    Database db = await open();

    int res = await db.update(sql_book_info, info.toMap(),
        where: "$sql_cataloglink = ?", whereArgs: [info.cataloglink]);
    //db.close();
    print(res);

    return res;
  }
}

class bookContentProvider {
  Future open() async {
    var databasesPath = await getDatabasesPath();
    String path = join(databasesPath, "read.db");
    print(path);

    Database db = await openDatabase(path, version: 1,
        onCreate: (Database db, int version) async {
      await db.execute('''
	 CREATE TABLE $sql_book_info (
	 $sql_id INTEGER NOT NULL primary key AUTOINCREMENT,
	 $sql_showname TEXT NOT NULL,
	 $sql_source TEXT NOT NULL,
	 $sql_cataloglink TEXT NOT NULL,
	 $sql_imagelink TEXT,
	 $sql_intro TEXT,
	 $sql_author TEXT,
	 $sql_lastread TEXT,
	 $sql_lastUrl TEXT,
	 $sql_keyword INTEGER,
	 $sql_link TEXT,
	 $sql_show INTEGER NOT NULL DEFAULT 0,
	 $sql_lastContentId INTEGER NOT NULL DEFAULT -1
);
      ''');
      await db.execute('''
CREATE TABLE $sql_book_content (
	 $sql_id INTEGER NOT NULL primary key AUTOINCREMENT,
	 $sql_bookid INTEGER,
	 $sql_chaptername TEXT,
	 $sql_content TEXT,
	 $sql_nextid INTEGER,
	 $sql_link TEXT,
	 $sql_source TEXT,
	 	 $sql_index INTEGER NOT NULL DEFAULT 0

	 );
      ''');
    });
    return db;
  }

  Future<BookContent> insert(BookContent content) async {
    Database db = await open();
    content.id = await db.insert(sql_book_content, content.toMap());
    //db.close();
    return content;
  }

  Future<int> insertContentList(List<BookContent> contentList) async {
    Database db = await open();
    Batch batch = db.batch();
    for (BookContent content in contentList) {
      batch.insert(sql_book_content, content.toMap());
    }
    await batch.commit(noResult: true);
    List<Map> res = await db.rawQuery('select MAX(id) from $sql_book_content');
    print(res[0]);
    BookContent temp = BookContent.fromMap(res[0]);

    return res[0]["MAX(id)"];
  }

  Future<BookContent> getBookContentWithLink(String link) async {
    print(link);
    Database db = await open();

    List<Map> maps = await db.query(sql_book_content,
        columns: [
          sql_id,
          sql_bookid,
          sql_chaptername,
          sql_content,
          sql_nextid,
          sql_link,
          sql_source,
          sql_index
        ],
        where: "$sql_link = ?",
        whereArgs: [link]);
    //db.close();
    if (maps.length > 0) {
      return BookContent.fromMap(maps.first);
    }
    return null;
  }

  Future<BookContent> getBookContentWithID(int id) async {
    Database db = await open();
    List<Map> maps = await db.query(sql_book_content,
        columns: [
          sql_id,
          sql_bookid,
          sql_chaptername,
          sql_content,
          sql_nextid,
          sql_link,
          sql_source,
          sql_index
        ],
        where: "$sql_id = ?",
        whereArgs: [id]);
    //db.close();
    if (maps.length > 0) {
      return BookContent.fromMap(maps.first);
    }

    return null;
  }

  Future<BookContent> getBookContentBookidAndIndex(
      int bookId, int index) async {
    Database db = await open();
    List<Map> maps = await db.query(sql_book_content,
        columns: [
          sql_id,
          sql_bookid,
          sql_chaptername,
          sql_content,
          sql_nextid,
          sql_link,
          sql_source,
          sql_index
        ],
        where: "$sql_bookid = ? and $sql_index = ?",
        whereArgs: [bookId, index]);
    //db.close();

    if (maps.length > 0) {
      return BookContent.fromMap(maps.first);
    }

    return null;
  }

  Future<BookContent> getBookContentWithNextId(int id) async {
    Database db = await open();
    List<Map> maps = await db.query(sql_book_content,
        columns: [
          sql_id,
          sql_bookid,
          sql_chaptername,
          sql_content,
          sql_nextid,
          sql_link,
          sql_source,
          sql_index
        ],
        where: "$sql_nextid = ?",
        whereArgs: [id]);
    //db.close();
    if (maps.length > 0) {
      return BookContent.fromMap(maps.first);
    }

    return null;
  }

  Future<List<BookContent>> getBookContentListWithBookID(int id) async {
    Database db = await open();
    List<Map> maps = await db.query(
      sql_book_content,
      columns: [
        sql_id,
        sql_bookid,
        sql_chaptername,
        sql_content,
        sql_nextid,
        sql_link,
        sql_source,
        sql_index
      ],
      where: "$sql_bookid = ?",
      whereArgs: [id],
      orderBy: "$sql_index",
    );
    //db.close();
    List<BookContent> bookContents = [];
    for (Map<String, dynamic> map in maps) {
      bookContents.add(BookContent.fromMap(map));
    }

    return bookContents;
  }

  Future<int> delete(int bookId) async {
    Database db = await open();

    int res = await db
        .delete(sql_book_content, where: "$sql_bookid=?", whereArgs: [bookId]);

    //db.close();
    return res;
  }

  Future<int> update(BookContent content) async {
    Database db = await open();
    int res = await db.update(sql_book_content, content.toMap(),
        where: "$sql_link = ?", whereArgs: [content.link]);
    //db.close();

    return res;
  }

  Future<BookContent> getLastBookContentWithBookId(int bookId) async {
    Database db = await open();
    List<Map> maps = await db.query(sql_book_content,
        columns: [
          sql_id,
          sql_bookid,
          sql_chaptername,
          sql_content,
          sql_nextid,
          sql_link,
          sql_source,
          sql_index,
        ],
        where: "$sql_bookid=? and $sql_nextid= ?",
        whereArgs: [bookId, -1]);
    //db.close();
    if (maps.length > 0) {
      return BookContent.fromMap(maps.first);
    }

    return null;
  }
}

class BookTools {
  //通过目录链接获取BookInfo(添加进入数据库，或者从数据库读取)
  Future<BookInfo> getBookInfoWithLink(Map<String, dynamic> book) async {
    BookInfo bookInfo =
        await BookInfoProvider().getBookInfoWithLink(book['CatalogLink']);
    if (bookInfo == null) {
      bookInfo = BookInfo();
      bookInfo.showname = book["BookName"];
      bookInfo.source = book["Source"];
      bookInfo.cataloglink = book["CatalogLink"];
      bookInfo.imagelink = book["Cover"];
      bookInfo.intro = book["Intro"];
      bookInfo.author = book["Author"];
      bookInfo.show = false;
      bookInfo.lastcontentid = -1;
      bookInfo = await BookInfoProvider().insert(bookInfo);
    }
    return bookInfo;
  }

  //更新目录列表（新建目录列表或者添加新章节
  Future<List<BookContent>> updateBookContentList(
      BookInfo bookInfo, List<dynamic> catalogList) async {
    BookContent lastContent;
    int index = -1;
    if (bookInfo.lastcontentid != -1) {
      lastContent = await bookContentProvider()
          .getBookContentWithID(bookInfo.lastcontentid);
      index = lastContent.chapterindex;
    }

    List<BookContent> contentList = [];
    for (int i = index + 1; i < catalogList.length; i++) {
      Map<String, dynamic> catalog = catalogList[i];
      BookContent bookContent = BookContent();
      bookContent.source = bookInfo.source;
      bookContent.bookid = bookInfo.id;
      bookContent.chaptername = catalog["CatalogName"];
      bookContent.link = catalog["Link"];
      bookContent.nextid = -1;
      bookContent.chapterindex = i;
      contentList.add(bookContent);
//      bookContent = await bookContentProvider().insert(bookContent);
    }
    if (contentList.length != 0) {
      int lastId = await bookContentProvider().insertContentList(contentList);
      bookInfo.lastcontentid = lastId;
      BookInfoProvider().update(bookInfo);
    }
    List<BookContent> bookContents =
        await bookContentProvider().getBookContentListWithBookID(bookInfo.id);
    return bookContents;
  }

//加入书架
  addBookShelf(BookInfo info) async {
    await BookInfoProvider().update(info);
  }
}

/*
阅读工具类
目前仅有分页
 */
class ReadUtils {
  TextPainter textPainter = TextPainter(textDirection: TextDirection.ltr);
  int times = 0;
  int length = 0;
  double height = 0;
  double width = 0;
  double fontSize = 0;
  String str;

  TextSpan getTextSpan(String text) {
    return new TextSpan(text: text, style: TextStyle(fontSize: fontSize));
  }

  //获取分页字数列表
  Future<List<Map<String, int>>> getPageList(
      String content, double h, double w, double fontsize) async {
    fontSize = fontsize;
    height = h;
    width = w;
    str = content;
    String tempStr = str;
    List<Map<String, int>> pageConfig = [];
    int last = 0;
    while (true) {
      Map<String, int> temp = {};
      temp["start"] = last;
      int end = await getPageLenght(tempStr);
      if (end == 0) {
        temp["end"] = last + tempStr.length;
        pageConfig.add(temp);
        break;
      }
      tempStr = tempStr.substring(end, tempStr.length);
      temp["end"] = last + end;
      last = temp["end"];
      pageConfig.add(temp);
    }
    return pageConfig;
  }

  //获取长度
  Future<int> getPageLenght(String content) async {
    if (load(content)) {
      return length;
    }
    return 0;
  }

  /// 接收内容
  /// 追加内容返回false
  /// 计算完毕返回true
  bool load(String text) {
    if (layout(text)) {
      return false;
    }

    int start = 0;
    int end = text.length;
    int mid = (end + start) ~/ 2;

    // 最多循环20次
    for (int i = 0; i < 20; i++) {
      if (layout(text.substring(0, mid))) {
        if (mid <= start || mid >= end) break;
        // 未越界
        start = mid;
        mid = (start + end) ~/ 2;
      } else {
        // 越界
        end = mid;
        mid = (start + end) ~/ 2;
      }
    }
    length = mid;
    return true;
  }

  //计算布局
  bool layout(String text) {
    times++;
    text = text ?? '';
    textPainter
      ..text = getTextSpan(text)
      ..layout(maxWidth: width);
    return !didExceed;
  }

  /// 是否超出边界
  bool get didExceed {
    return textPainter.didExceedMaxLines || textPainter.size.height > height;
  }
}
